#include "GImage.h"

namespace GEngine
{
	GUInt8 ImageFormatBpps[G_TF_COUNT] = 
	{
		0,	//G_TF_NONE
		1,	//G_TF_SINT_R8
		2,	//G_TF_SINT_R16
		4,	//G_TF_SINT_R32
		2,	//G_TF_SINT_R8G8
		4,	//G_TF_SINT_R16G16
		8,	//G_TF_SINT_R32G32
		4,	//G_TF_SINT_R8G8B8A8
		8,	//G_TF_SINT_R16G16B16A16
		16,	//G_TF_SINT_R32G32B32A32
		
		1,	//G_TF_UINT_R8
		2,	//G_TF_UINT_R16
		4,	//G_TF_UINT_R32
		2,	//G_TF_UINT_R8G8
		4,	//G_TF_UINT_R16G16
		8,	//G_TF_UINT_R32G32
		4,	//G_TF_UINT_R8G8B8A8
		8,	//G_TF_UINT_R16G16B16A16
		16,	//G_TF_UINT_R32G32B32A32

		1,	//G_TF_SNORM_R8
		2,	//G_TF_SNORM_R16
		2,	//G_TF_SNORM_R8G8
		4,	//G_TF_SNORM_R16G16
		4,	//G_TF_SNORM_R8G8B8A8
		8,	//G_TF_SNORM_R16G16B16A16

		1,	//G_TF_UNORM_R8
		2,	//G_TF_UNORM_R16
		2,	//G_TF_UNORM_R8G8
		4,	//G_TF_UNORM_R16G16
		4,	//G_TF_UNORM_R8G8B8A8
		8,	//G_TF_UNORM_R16G16B16A16

		2,	//G_TF_FLOAT_R16
		4,	//G_TF_FLOAT_R32
		4,	//G_TF_FLOAT_R16G16
		8,	//G_TF_FLOAT_R32G32
		8,	//G_TF_FLOAT_R16G16B16A16
		16,	//G_TF_FLOAT_R32G32B32A32

		0,	//G_TF_UNORM_BC1
		0,	//G_TF_UNORM_BC2
		0,	//G_TF_UNORM_BC3
		0,	//G_TF_SNORM_BC4
		0,	//G_TF_UNORM_BC4
		0,	//G_TF_SNORM_BC5
		0,	//G_TF_UNORM_BC5
		0,	//G_TF_UFLOAT_BC6H
		0,	//G_TF_SFLOAT_BC6H
		0,	//G_TF_UNORM_BC7

		0,	//G_TF_SHADOW_NULL
		0,	//G_TF_SHADOW_INTZ
	};


	GImage::GImage()
	{
		Buffer = NULL;
		Size = 0;
		
		Bpp = 0;
		Format = G_TF_NONE;
	}

	GImage::~GImage()
	{
	}

	GSize GImage::GetBpp() const
	{
		return Bpp;
	}

	GSize GImage::GetSize() const
	{
		return Size;
	}

	GSize GImage::GetWidth() const
	{
		return Dimension.W;
	}

	GSize GImage::GetHeight() const
	{
		return Dimension.H;
	}

	GSize GImage::GetDepth() const
	{
		return Dimension.D;
	}

	GSize GImage::GetPitch() const
	{
		return Dimension.W * Bpp;
	}

	GSize GImage::GetDepthPitch() const
	{
		return Dimension.W * Dimension.H * Bpp;
	}

	GTextureFormat GImage::GetFormat() const
	{
		return Format;
	}

	GUInt8* GImage::GetBuffer(GSize Depth) const
	{
		GSize DepthPitch = GetDepthPitch();
		GSize Offset = DepthPitch * Depth;

		return Buffer + Offset;
	}

	void GImage::CopyTo(const GImageCoord& SourceCoord, const GImageDimension& CopyDimension, const GImageCoord& TargetCoord, GImage* TargetImage)
	{
		GUtil::GError::DebugCheck(Buffer == NULL, "Null pointer");
		GUtil::GError::DebugCheck(TargetImage == NULL, "NULL pointer.");

		GUtil::GError::DebugCheck(SourceCoord.X + CopyDimension.W > Dimension.W, "Source dimension X too big");
		GUtil::GError::DebugCheck(SourceCoord.Y + CopyDimension.H > Dimension.H, "Source dimension Y too big");
		GUtil::GError::DebugCheck(SourceCoord.Z + CopyDimension.D > Dimension.D, "Source dimension Z too big");
		GUtil::GError::DebugCheck(TargetCoord.X + CopyDimension.W > TargetImage->Dimension.W, "Target dimension X too big");
		GUtil::GError::DebugCheck(TargetCoord.Y + CopyDimension.H > TargetImage->Dimension.H, "Target dimension Y too big");
		GUtil::GError::DebugCheck(TargetCoord.Z + CopyDimension.D > TargetImage->Dimension.D, "Target dimension Z too big");

		GSize SourcePitch = GetPitch();
		GSize SourceDepthPitch = GetDepthPitch();
		
		GSize TargetPitch = TargetImage->GetPitch();
		GSize TargetDepthPitch = TargetImage->GetDepthPitch();

		if ((CopyDimension.W == Dimension.W) && (CopyDimension.H == Dimension.H))
		{
			// X any Y dimensions match, results in a single copy
			GSize CopySize = SourceDepthPitch * CopyDimension.D;
			memcpy(TargetImage->Buffer, Buffer, CopySize);
		}
		else if (CopyDimension.W == Dimension.W)
		{
			// Only X dimension match, results in single copy for each depth
			GSize CopySize = SourcePitch * CopyDimension.H;
			GUInt8* Source = Buffer + SourcePitch * SourceCoord.Y;
			GUInt8* Target = TargetImage->Buffer + TargetPitch * TargetCoord.Y;
			
			// For each depth
			for (GSize Z = 0; Z < CopyDimension.D; ++Z)
			{
				// Copy row
				memcpy(Target, Source, CopySize);

				Source += SourceDepthPitch;
				Target += TargetDepthPitch;
			}
		}
		else
		{
			// No shortcut, copy row by row
			GSize CopySize = CopyDimension.W * Bpp;
			GUInt8* SourceDepth = Buffer + SourcePitch * SourceCoord.Y;
			GUInt8* TargetDepth = TargetImage->Buffer + TargetPitch * TargetCoord.Y;

			// For each depth
			for (GSize Z = 0; Z < CopyDimension.D; ++Z)
			{
				GUInt8* Source = SourceDepth + SourceCoord.Y * SourcePitch + SourceCoord.X * Bpp;
				GUInt8* Target = TargetDepth + TargetCoord.Y * TargetPitch + TargetCoord.X * Bpp;
				
				// For each row
				for (GSize Y = 0; Y < CopyDimension.H; ++Y)
				{
					// Copy row
					memcpy(Target, Source, CopySize);

					// Increment row
					Source += SourcePitch;
					Target += TargetPitch;
				}

				// Increment depth
				SourceDepth += SourceDepthPitch;
				TargetDepth += TargetDepthPitch;
			}
		}

	}

	void GImage::CopyFrom(const GImageCoord& TargetCoord, const GImageDimension& SourceDimension, const GImageCoord& SourceCoord, GUInt8* SourceImage)
	{
		GUtil::GError::DebugCheck(Buffer == NULL, "Not created.");
		GUtil::GError::DebugCheck(SourceImage == NULL, "NULL pointer.");

		// Use copyto method
		GImage TempImage;

		TempImage.Create(SourceDimension, Format, SourceImage);
		TempImage.CopyTo(TargetCoord ,SourceDimension, SourceCoord, this);

		TempImage.Release(true);
	}

	GUInt8* GImage::Release(bool ReclaimBuffer)
	{
		GUInt8* TmpBuffer = Buffer;

		if (!ReclaimBuffer)
		{
			// Delete if not reclaimed
			G_DELETE_ARR(Buffer);
			TmpBuffer = NULL;
		}

		Buffer = NULL;
		Size = 0;

		Dimension.W = 0;
		Dimension.H = 0;
		Dimension.D = 0;
		
		Bpp = 0;
		Format = G_TF_NONE;

		return TmpBuffer;
	}

	// Creates and allocates memory. Does not copy any source data
	bool GImage::Create(const GImageDimension& ImageDimension, GTextureFormat ImageFormat)
	{
		GUInt8 ImageBpp = GetFormatBpp(ImageFormat);

		// Checks
		GUtil::GError::DebugCheck(Buffer != NULL, "Already created.");
		GUtil::GError::DebugCheck(ImageBpp == 0, "Cannot allocate for requested format.");
		
		GSize PixelCount = ImageDimension.GetPixelCount();
		GSize BufferSize = PixelCount * ImageBpp;

		// Create
		Buffer = new GUInt8[BufferSize];
		Size = BufferSize;
		Bpp = ImageBpp;
		Format = ImageFormat;
		Dimension = ImageDimension;
		
		return true;
	}

	// Creates image, allocates memory and Takes ownership of Image Buffer.
	// If loosing ownership is not desired then create with allocation and use CopyFrom to copy data.
	bool GImage::Create(const GImageDimension& ImageDimension, GTextureFormat ImageFormat, GUInt8* ImageBuffer)
	{
		GUInt8 ImageBpp = GetFormatBpp(ImageFormat);

		// Checks
		GUtil::GError::DebugCheck(Buffer != NULL, "Already created.");
		GUtil::GError::DebugCheck(ImageBuffer == NULL, "Null pointer.");
		GUtil::GError::DebugCheck(ImageBpp == 0, "Cannot allocate for requested format.");
		
		GSize PixelCount = ImageDimension.GetPixelCount();
		GSize BufferSize = PixelCount * ImageBpp;

		// Create
		Buffer = ImageBuffer;
		Size = BufferSize;
		Bpp = ImageBpp;
		Format = ImageFormat;
		Dimension = ImageDimension;
		
		return true;
	}

	GUInt8 GImage::GetFormatBpp(GTextureFormat Format)
	{
		return ImageFormatBpps[Format];
	}

}